#include "Display.h"
#include "ResDef.h"
#include "CustomException.h"

#include <thread>
#include <dwmapi.h>
#include <versionhelpers.h>
#include <mfobjects.h>

namespace core{
    namespace display{
        using namespace util::tool;

        template HRESULT AbstractPlayer::SafeCall<HRESULT>(HRESULT (*)(AbstractPlayer*));

        template HRESULT AbstractPlayer::SafeCall<HRESULT, IMFMediaEvent *>(
            typename tool::MultFunction<HRESULT, AbstractPlayer*, IMFMediaEvent *>::value func,
            IMFMediaEvent * pEvent
        );

        ResourceFactory::ResourceFactory() : pIWICFactory(nullptr), pSource(nullptr){

        }

        HRESULT ResourceFactory::Initialize(){
            if(pIWICFactory == nullptr)
                return CoCreateInstance(
                    CLSID_WICImagingFactory,
                    NULL,
                    CLSCTX_INPROC_SERVER,
                    IID_PPV_ARGS(&pIWICFactory));
            
            return E_FAIL;
        }

        bool ResourceFactory::HasInitialized(){
            return pIWICFactory != nullptr;
        }

        bool ResourceFactory::IsBuilding(){
            return pSource != nullptr;
        }


        HRESULT ResourceFactory::GetWICBitmapFromId(int id){
            if(pSource != nullptr)
                return E_FAIL;
            res::BinFile bin;
            res::ReadBinFileFromStaticMemory(
                &_binary_resbuilder_resource_data_start,
                bin,
                id
            );
            return GetWICBitmapFromMemory(&bin);
        }

        HRESULT ResourceFactory::GetWICBitmapFromMemory(res::BinFile *pbin){
            HRESULT hr;
            IWICStream * pStream;
            IWICBitmapDecoder * pDecoder;
            IWICBitmapFrameDecode * pDecodeSource;
            IWICFormatConverter * pConvert;
            hr = pIWICFactory->CreateStream(&pStream);
            if(!SUCCEEDED(hr))
                goto release;
            hr = pStream->InitializeFromMemory(
                reinterpret_cast<BYTE*>(pbin->data),
                static_cast<DWORD>(pbin->size)
            );
            if(!SUCCEEDED(hr))
                goto release;
            hr = pIWICFactory->CreateDecoderFromStream(
                pStream,
                NULL,
                WICDecodeMetadataCacheOnLoad,
                &pDecoder
            );
            if(!SUCCEEDED(hr))
                goto release;
            hr = pDecoder->GetFrame(0, &pDecodeSource);
            if(!SUCCEEDED(hr))
                goto release;
            hr = pIWICFactory->CreateFormatConverter(&pConvert);
            if(!SUCCEEDED(hr))
                goto release;
            hr = pConvert->Initialize(
                pDecodeSource,
                GUID_WICPixelFormat32bppPBGRA,
                WICBitmapDitherTypeNone,
                NULL,
                0,
                WICBitmapPaletteTypeCustom
            );
            pSource = static_cast<IWICBitmapSource *>(pConvert);
release:
            pConvert = nullptr;
            SafeRelease(&pStream);
            SafeRelease(&pDecoder);
            SafeRelease(&pDecodeSource);
            return hr;
        }

        HRESULT ResourceFactory::CreateD2DBitmap(ID2D1Bitmap ** ppbmp, ID2D1RenderTarget* pTarget){
            if(pSource == nullptr)
                return E_FAIL;
            pTarget->CreateBitmapFromWicBitmap(pSource, ppbmp);
            SafeRelease(&pSource);
            return S_OK;
        }

        ResourceFactory::~ResourceFactory(){
            SafeRelease(&pIWICFactory);
            SafeRelease(&pSource);
        }

        ResourceFactory INSTANCE;

        AbstractPlayer::AbstractPlayer() : 
            player_lock(), 
            current_lock_ptr(nullptr),
            pListener(nullptr){

        }

        bool AbstractPlayer::SetEventListener(msg::IUniqueMessageHandler * plistener){
            return plistener->SetPtr(&pListener);
        }

        template<typename T>
        T AbstractPlayer::SafeCall(T (*consumer)(AbstractPlayer * player)){
            std::unique_lock smart_lock(player_lock);
            current_lock_ptr = &smart_lock;
            T result = consumer(this);
            current_lock_ptr = nullptr;
            return result;
        }

        template<>
        void AbstractPlayer::SafeCall<void>(void (*consumer)(AbstractPlayer * player)){
            std::unique_lock smart_lock(player_lock);
            current_lock_ptr = &smart_lock;
            consumer(this);
            current_lock_ptr = nullptr;
        }

        template<typename T>
        T AbstractPlayer::SafeCall(T (AbstractPlayer::*consumer)()){
            std::unique_lock smart_lock(player_lock);
            current_lock_ptr = &smart_lock;
            T result = (this->*consumer)();
            current_lock_ptr = nullptr;
            return result;
        }

        template<>
        void AbstractPlayer::SafeCall<void>(void (AbstractPlayer::*consumer)()){
            std::unique_lock smart_lock(player_lock);
            current_lock_ptr = &smart_lock;
            (this->*consumer)();
            current_lock_ptr = nullptr;
        }

        template<typename T, typename First, typename ...Arg>
        T AbstractPlayer::SafeCall(
            typename tool::MultFunction<T, AbstractPlayer *, First, Arg...>::value func, 
            First arg1,
            Arg... args){
            std::unique_lock smart_lock(player_lock);
            current_lock_ptr = &smart_lock;
            T result = func(this, arg1, args...);
            current_lock_ptr = nullptr;
            return result;
        }


        AnimationProcess::AnimationProcess(const D2D1_RECT_F& rect) : 
            frame_count(0),
            prender(nullptr),
            bmp_quene(nullptr),
            playing_bmp_iptr(0),
            rect(rect),
            mode(0),
            stats(PlayStatus::Idle),
            frame_interval(0.08f),
            last_t(clock()){}

        AnimationProcess::AnimationProcess() : AnimationProcess(D2D1::RectF(0,0,200,200)){
        }

        // void AnimationProcess::SetLock(const std::mutex& lock){
        //     pplayer_lock = const_cast<std::mutex *>(&lock);
        // }
        
        void AnimationProcess::SetRect(const D2D1_RECT_F& rect){
            this->rect = rect;
        }

        void AnimationProcess::SetRender(ID2D1RenderTarget * prender){
            this->prender = prender;
        }

        void AnimationProcess::SetInterval(float itrl){
            this->frame_interval = itrl;
        }

        HRESULT AnimationProcess::Prepare(int * id_arr, int count){
            if(bmp_quene != nullptr){
                for(int i = 0; i < frame_count; i++)
                    SafeRelease(&bmp_quene[i]);
                delete[] bmp_quene;
            }
            bmp_quene = new ID2D1Bitmap*[count];
            if(INSTANCE.IsBuilding() || prender == nullptr)
                return E_FAIL;
            for(int i = 0; i < count; i++){
                INSTANCE.GetWICBitmapFromId(id_arr[i]);
                INSTANCE.CreateD2DBitmap(bmp_quene + i, prender);
            }
            frame_count = count;
            stats = PlayStatus::Ready;
            return S_OK;
        }

        HRESULT AnimationProcess::Prepare(int from_id, int to_id){
            if(bmp_quene != nullptr){
                for(int i = 0; i < frame_count; i++)
                    SafeRelease(&bmp_quene[i]);
                delete[] bmp_quene;
            }
            frame_count = to_id - from_id + 1;
            bmp_quene = new ID2D1Bitmap*[frame_count];
            res::BinFile * bin_quene = new res::BinFile[frame_count];
            if(INSTANCE.IsBuilding() || prender == nullptr)
                return E_FAIL;
            res::ReadBinFileFromStaticMemory(
                &_binary_resbuilder_resource_data_start,
                bin_quene,
                from_id,
                to_id
            );
            for(int i = 0; i < frame_count; i++){
                INSTANCE.GetWICBitmapFromMemory(bin_quene + i);
                INSTANCE.CreateD2DBitmap(bmp_quene + i, prender);
            }
            stats = PlayStatus::Ready;
            return S_OK;
        }

        //thread unsafe
        HRESULT AnimationProcess::Prepare(ID2D1Bitmap **parr, int count){
            if(bmp_quene != nullptr){
                for(int i = 0; i < frame_count; i++)
                    SafeRelease(&bmp_quene[i]);
                delete[] bmp_quene;
            }
            bmp_quene = new ID2D1Bitmap*[count];
            for(int i = 0; i < count; i++){
                bmp_quene[i] = parr[i];
                parr[i] = nullptr;
            }
            frame_count = count;
            stats = PlayStatus::Ready;
            return S_OK;   
        }

        void AnimationProcess::Reset(){
            playing_bmp_iptr = 0;
            if(stats == PlayStatus::Playing || stats == PlayStatus::Stopped)
                stats = PlayStatus::Ready;
        }

        bool AnimationProcess::IsReadyForNextFrame(){
            return difftime(clock(),last_t) > frame_interval * 1000;
        }

        PlayStatus AnimationProcess::GetStatus(){
            return stats;
        }

        D2D1_RECT_F AnimationProcess::GetRect(){
            return rect;
        }

        HRESULT AnimationProcess::Play(){
            stats = PlayStatus::Playing;
            return S_OK;
        }
        
        HRESULT AnimationProcess::Paint(){
            if(IsReadyForNextFrame()){
                if(PlayNextFrame())
                    return S_OK;
                else
                    stats = PlayStatus::Stopped;
            }
            return S_FALSE;
        }

        bool AnimationProcess::PlayNextFrame(){
            if(stats == PlayStatus::Stopped){   
                return false;
            }
            D2D1_RECT_F buf_rect = rect;
            if(mode & AnimationProcessMode::CONSTRAINED){
                int bx = bmp_quene[playing_bmp_iptr]->GetSize().width, by = bmp_quene[playing_bmp_iptr]->GetSize().height;
                if((buf_rect.right - buf_rect.left) * by > (buf_rect.bottom - buf_rect.top) * bx){
                    int r = (buf_rect.right - buf_rect.left) / bx * by / 2;
                    int mid = (buf_rect.bottom - buf_rect.top) / 2;
                    buf_rect.top = mid - r;
                    buf_rect.bottom = mid + r; 
                }
                else{
                    int r = (buf_rect.bottom - buf_rect.top) / by * bx / 2;
                    int mid = (buf_rect.right - buf_rect.left) / 2;
                    buf_rect.left = mid - r;
                    buf_rect.right = mid + r; 
                }
            }
            last_t = clock();
            prender->BeginDraw();
            prender->DrawBitmap(
                bmp_quene[playing_bmp_iptr++],
                buf_rect
            );
            prender->EndDraw();
            if(playing_bmp_iptr >= frame_count && stats == PlayStatus::Playing){
                playing_bmp_iptr = frame_count - 1;
                stats = PlayStatus::Stopped;
                ClearWhatItPaint();
            }
            return playing_bmp_iptr < frame_count;
        }
        
        void AnimationProcess::ClearWhatItPaint(){
            prender->BeginDraw();
            prender->Clear(NULL);
            prender->EndDraw();
        }

        bool AnimationProcess::GetCurrentFrameD2DBitmap(ID2D1Bitmap ** ppbmp){
            if(playing_bmp_iptr >= frame_count)
                *ppbmp = bmp_quene[playing_bmp_iptr - 1];
            else
                *ppbmp = bmp_quene[playing_bmp_iptr];
            return true;
        }

        // std::future<bool> AnimationProcess::AsycStartPlay(bool * is_playing){
        //     if(this->pplayer_lock == nullptr)
        //         throw "can't play async: failed to get lock";
        //     return std::async(std::launch::async,
        //         [is_playing, this]()->bool{
        //             bool has_frame = true;
        //             this->pplayer_lock->lock();
        //             this->Reset();
        //             while(*is_playing && has_frame){
        //                 if(this->IsReadyForNextFrame()){
        //                     has_frame = this->PlayNextFrame();
        //                 }
        //             }  
        //             this->Reset();
        //             this->pplayer_lock->unlock();
        //             *is_playing = false;
        //             return true;
        //         }
        //     );
        // }
    

        AnimationProcess::~AnimationProcess(){
            if(bmp_quene != nullptr){
                for(int i = 0; i < frame_count; i++)
                    SafeRelease(&bmp_quene[i]);
                delete[] bmp_quene;
            }
        }

        AnimationManager::AnimationManager() : 
            is_playing_handle(false),
            is_preparing(false),
            pplayer(nullptr){}
        
        AnimationManager::~AnimationManager(){
        }

        bool AnimationManager::IsPlaying(){
            return is_playing_handle;
        }

        void AnimationManager::Play(AbstractPlayer * player){
            // is_preparing = true;
            // is_playing_handle = false;
            // if(this->when_stop == nullptr){
            //     this->is_playing_handle = true;
            //     this->pplayer = player;
            //     this->when_stop = new std::future(pplayer->AsycStartPlay(&is_playing_handle));
            // }
            // else {
            //     if(!this->when_stop->valid() || this->when_stop->get()){
            //         this->is_playing_handle = true;
            //         this->pplayer = player;
            //         *this->when_stop = this->pplayer->AsycStartPlay(&is_playing_handle);
            //     }
            // }
            // is_preparing = false;
            if(is_playing_handle){
                pplayer->SafeCall<void>(AbstractPlayer::Reset);
            }
            is_playing_handle = true;
            pplayer = player;
            if(!pplayer->SetEventListener(this))
                throw util::exception::UnexpectedException("failed to set Animation Listener");
            player->Play();
        }

        void AnimationManager::OnPaint(){
            if(is_playing_handle){
                is_playing_handle = pplayer->SafeCall<bool>([](AbstractPlayer* pplayer){
                    if(pplayer->GetStatus() == PlayStatus::Playing){
                        HRESULT hr = pplayer->Paint();
                        if(SUCCEEDED(hr))
                            return true;
                    }
                    return false;
                });
            }
        }

        bool AnimationManager::Receive(util::message::MessageType msg, void * param){
            using namespace util::message;
            switch (msg)
            {
                case MessageType::AM_PLAYER_STOPPED : {
                    pplayer->SafeCall<void>(AbstractPlayer::Reset);
                    is_playing_handle = false;
                    return true;
                }
            
            default:
                break;
            }
            return false;
        }
        
        bool EnableAlphaCompositing(HWND hWnd)
        {
            if (!::IsWindowsVistaOrGreater()) { return false; }

            BOOL is_composition_enable = false;
            ::DwmIsCompositionEnabled(&is_composition_enable);
            if (!is_composition_enable) { return true; }

            DWORD current_color = 0;
            BOOL is_opaque = false;
            ::DwmGetColorizationColor(&current_color, &is_opaque);

            if (!is_opaque || IsWindows8OrGreater())
            {
                HRGN region = ::CreateRectRgn(0, 0, -1, -1);
                DWM_BLURBEHIND bb = { 0 };
                bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
                bb.hRgnBlur = region;
                bb.fEnable = TRUE;
                ::DwmEnableBlurBehindWindow(hWnd, &bb);
                ::DeleteObject(region);
                return true;
            }
            else // For Window7
            {
                DWM_BLURBEHIND bb = { 0 };
                bb.dwFlags = DWM_BB_ENABLE;
                ::DwmEnableBlurBehindWindow(hWnd, &bb);
                return false;
            }
        } 
    }
}